Skip to content

o/ifacestate: properly handle undo scenarios where auto-connections are dropped - #17018

Merged
andrewphelpsj merged 6 commits into
canonical:masterfrom
andrewphelpsj:fix-connection-undo
May 11, 2026
Merged

o/ifacestate: properly handle undo scenarios where auto-connections are dropped#17018
andrewphelpsj merged 6 commits into
canonical:masterfrom
andrewphelpsj:fix-connection-undo

Conversation

@andrewphelpsj

Copy link
Copy Markdown
Member

When a refresh moves to a revision that drops an auto-connected plug, reloadConnections removes that auto-connection from the connection state. If the refresh fails, the deleted auto-connection is not restored. This leaves the interface disconnected after the undo has completed.

This only impacts auto-connections because manual connections whose plug or slot is missing in the new revision are kept in the connection state, but auto-connections are pruned.

A similar idea applies to static plug and slot attributes from the new revision. If the refresh fails, rollback restores the old snap revision, but the new revision's static attributes are kept in the connection state.

To solve this problem, this PR updates setup-profiles so that it keeps track of connection state changes and restores them in the undo handler.

@andrewphelpsj
andrewphelpsj force-pushed the fix-connection-undo branch from 8447578 to 35bc2c7 Compare May 4, 2026 21:56
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Mon May 11 15:19:58 UTC 2026
The following results are from: https://github.com/canonical/snapd/actions/runs/25442970542

Failures:

Preparing:

  • openstack:opensuse-16.0-64:
  • openstack:opensuse-16.0-64:
  • openstack:opensuse-16.0-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:

Executing:

  • openstack:debian-sid-64:tests/unit/go:static

Restoring:

  • openstack:opensuse-16.0-64:
  • openstack:opensuse-16.0-64:
  • openstack:opensuse-16.0-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:
  • openstack:opensuse-15.6-64:

Skipped tests from snapd-testing-skip

If you wish to have any of the below tests run in your PR, in your PR description, add 'unskip:' followed by a copy-and-pasted list (without variants) of the below tests you wish to run (unskip plus test list must be valid yaml)

  • garden:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack-arm:ubuntu-24.04-arm-64:tests/main/i18n
  • openstack-arm:ubuntu-core-24-arm-64:tests/main/i18n
  • openstack:centos-9-64:tests/main/selinux-clean
  • openstack:debian-sid-64:tests/main/interfaces-network-status-classic
  • openstack:fedora-42-64:tests/main/selinux-clean
  • openstack:ubuntu-24.04-64:tests/main/i18n
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_fd_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_path_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:create_write_write_same_path_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_conflict
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_defaults
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:download_file_safer
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:read_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:read_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:special_characters
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:timespan_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:timespan_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_allow_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_deny_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_single_allow
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests:write_single_deny
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:camera_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_allow_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_forever
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_session
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_single
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke:home_deny_timespan
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-25.10-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_actioned_by_other_pid_always_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_multiple_not_actioned_by_other_pid_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_fd_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_write_chmod_same_path_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:create_write_write_same_path_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_conflict
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_defaults
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:download_file_safer
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:read_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:read_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:special_characters
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:timespan_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:timespan_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_allow_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_read_multiple_actioned_by_other_pid_deny_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_single_allow
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests:write_single_deny
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:audiorecord_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:camera_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_allow_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_forever
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_session
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_single
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke:home_deny_timespan
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-26.04-64:tests/main/i18n
  • openstack:ubuntu-26.04-64:tests/main/interfaces-requests-activates-handlers

@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.62791% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.07%. Comparing base (6ef8a8e) to head (d56f83c).
⚠️ Report is 31 commits behind head on master.

Files with missing lines Patch % Lines
overlord/ifacestate/helpers.go 65.75% 20 Missing and 5 partials ⚠️
overlord/ifacestate/handlers.go 41.66% 4 Missing and 3 partials ⚠️
overlord/ifacestate/ifacemgr.go 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #17018      +/-   ##
==========================================
+ Coverage   79.04%   79.07%   +0.02%     
==========================================
  Files        1375     1376       +1     
  Lines      191012   191330     +318     
  Branches     2465     2465              
==========================================
+ Hits       150981   151285     +304     
- Misses      30924    30933       +9     
- Partials     9107     9112       +5     
Flag Coverage Δ
unittests 79.07% <61.62%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming question

Comment thread overlord/ifacestate/helpers.go Outdated
originalConns[connID] = connState
}

task.Set("original-connection-states", originalConns)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a strange name as this is not the full set of original connections, not we want it to be that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this, and renamed a few other things. Using changed-connection-snapshot now.

@andrewphelpsj andrewphelpsj added this to the 2.76 milestone May 5, 2026
// The connection refers to a plug or slot that doesn't exist anymore, e.g. because of a refresh
// to a new snap revision that doesn't have the given plug/slot.
if plugInfo == nil || slotInfo == nil {
// automatic connection can simply be removed (it will be re-created automatically if needed)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewphelpsj fix this comment

@andrewphelpsj
andrewphelpsj requested a review from pedronis May 5, 2026 13:27

@bboozzoo bboozzoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, had a chat with @andrewphelpsj to discuss the details

@andrewphelpsj
andrewphelpsj requested a review from Meulengracht May 5, 2026 15:46
Comment thread overlord/ifacestate/handlers.go Outdated
Comment thread overlord/ifacestate/helpers.go
Comment thread overlord/ifacestate/helpers.go Outdated
Comment thread overlord/ifacestate/helpers.go Outdated
Comment thread overlord/ifacestate/helpers.go Outdated
Comment thread overlord/ifacestate/ifacestate_test.go Outdated
Comment thread tests/main/connections-after-failed-refresh/task.yaml Outdated
Comment on lines +380 to +384
if task.Status() != state.UndoingStatus {
if err := snapshotChangedConnectionsForUndo(task, snapName, changedConns); err != nil {
return nil, nil, err
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is super convenient place to put this code, it creates some implicit behaviour of this function that is really not obvious, and should as a minimum be documented. Ideally I'd rather see this as a return value an handled on a task level instead, but I recognize the annoying thing about changing the call chain that also goes through setupProfilesForAppSet

@Meulengracht Meulengracht May 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a completely naive thought: is there any reason we don't just cache the all the refreshed snaps connections from "conns" state at the start of Do and restore this during undo?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't wrong, it would be much more explicit. The catch is we don't know which ones are relevant until quite late, so we'd need to collect all of the connections of a snap, along with information on how they were established. Not great but doable. For some snaps, like snapd there's quite a lot of data to be stored this way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a commit renaming some things and adding some comments. I agree that making the task handler itself store this data on the task would be nice, but that would require a pretty large refactor I think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's that large a refactor, but it's also something we can do at another stage.

Comment on lines +709 to +711
if err := restoreConnectionsForSetupProfiles(task); err != nil {
return err
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should mention that it relies on the task variable being set by refreshAppSetConnections during the do path.

@Mohit-Chachada Mohit-Chachada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment thread tests/main/connections-after-failed-refresh/task.yaml Outdated

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question

Comment thread overlord/ifacestate/helpers.go Outdated
func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, err error) {
// The return value is the list of reloaded connection IDs, plus the original
// connection states whose persisted state was changed.
func (m *InterfaceManager) reloadConnections(snapName string) (reloadedConnectionIDs []string, changedConns map[string]*schema.ConnState, err error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose changedConns includes also dropped connections?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewphelpsj andrewphelpsj May 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it contains both deleted connections and connections whose static attributes were updated.

@andrewphelpsj
andrewphelpsj force-pushed the fix-connection-undo branch from e8f9829 to d56f83c Compare May 6, 2026 14:54
Comment on lines +380 to +384
if task.Status() != state.UndoingStatus {
if err := snapshotChangedConnectionsForUndo(task, snapName, changedConns); err != nil {
return nil, nil, err
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's that large a refactor, but it's also something we can do at another stage.

@andrewphelpsj andrewphelpsj added the Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system label May 11, 2026
@andrewphelpsj
andrewphelpsj merged commit 496d4a1 into canonical:master May 11, 2026
398 of 418 checks passed
@andrewphelpsj
andrewphelpsj deleted the fix-connection-undo branch May 11, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants